/*
 * Copyright 2007-2008 Michele Mostarda ( michele.mostarda@gmail.com ).
 * All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the 'License');
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an 'AS IS' BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */


package org.asemantics.anthill;

import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.asemantics.anthill.chatconsole.ChatConsoleOutputStream;
import org.asemantics.anthill.chatconsole.Command;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Defines a Command to be sent to a {@link org.asemantics.anthill.CommandListenerTask} task.
 */
public class Invoke extends RemoteOperation implements Command {

    /**
     * Defines a parameter of a command.
     * A parameter is a couple of name / value strings.
     */
    public class Parameter {

        /* BEGIN: Parameter node attributes. */

        /**
         * The name of the parameter.
         */
        String name;

        /**
         * The value of the parameter.
         */
        String value;

        /* END: Parameter node attributes. */

        /**
         * Sets the name of the parameter.
         *
         * @param name
         */
        public void setName(String name) {
            CommanderTask.validateAttribute(name, "name");
            this.name = name;
        }

        /**
         * Sets the value of the parameter.
         *
         * @param value
         */
        public void setValue(String value) {
            CommanderTask.validateAttribute(value, "value");
            this.value = value;
        }
    }

    /* BEGIN: Command node attributes. */

    /**
     * The command name.
     */
    private String name;

    /* END: Command node attributes. */


    private final List<Parameter> parameters = new ArrayList();

    /**
     * Sets the command name.
     *
     * @param name
     */
    public void setName(String name) {
        CommanderTask.validateAttribute(name, "name");
        this.name = name;
    }

    /**
     * Returns the command name.
     *
     * @return
     */
    public String getName() {
        return this.name;
    }

    /**
     * Creates a parameter object. The <i>create</i>
     * pattern is used by <i>Ant</i> to define nested nodes
     * into a {@link org.apache.tools.ant.Task}.
     *
     * @return
     */
    public Parameter createParameter() {
        Invoke.Parameter parameter = new Invoke.Parameter();
        parameters.add(parameter);
        return parameter;
    }

    /**
     * The command <i>XMPP</i> message to be sent.
     */
    private CommandMessage commandMessage;

    /**
     * Prepares a {@link org.asemantics.anthill.CommandMessage} containing the parameters defined
     * by this Command.
     *
     * @return
     */
    public CommandMessage prepareMessageInternal() {
        if (commandMessage == null) {
            Map<String, Object> parametersMap = new HashMap();
            for (Invoke.Parameter p : parameters) {
                if (parametersMap.containsKey(p.name)) {
                    throw new IllegalArgumentException(
                            "Error in message " + getName() + ":" + " parameter " + p.name + " defined twice."
                    );
                }
                parametersMap.put(p.name, p.value);
            }
            commandMessage = new InvokeCommandMessage(getName(), parametersMap);
        }
        return commandMessage;
    }

    public CommandResponseMessage processCommand(CommandListenerTask clt, CommandMessage commandMessage) {
        Throwable result = executeCommand(clt, (InvokeCommandMessage) commandMessage);
        CommandResponseMessage resultMessage = new InvokeCommandResponseMessage( result, clt.collectProperties(), true);
        return resultMessage;
    }

    public void processResult(final CommanderTask commanderTask, final CommandResponseMessage response) {
        Map<String, String> returnProperties = InvokeCommandResponseMessage.getReturnParameters(response);
        commanderTask.loadPropertiesInProject(returnProperties);
    }

    public boolean execute(CommandListenerTask clt, ChatConsoleOutputStream chatConsole, String commandName, String[] arguments) {
        // Extracts parameters.
        String propertyName, propertyValue;
        Map<String,String> parameters = new HashMap();
        int splitpoint;
        for (String argument : arguments) {
            splitpoint = argument.indexOf('=');
            propertyName = argument.substring(0, splitpoint);
            propertyValue = argument.substring(splitpoint + 1);
            // System.out.println("P: '" + propertyName.trim() + "'");
            // System.out.println("V: '" + propertyValue.trim() + "'");
            parameters.put(propertyName.trim(), propertyValue.trim());
        }

        // Invokes command.
        Throwable t = executeCommand(clt, commandName, parameters);
        if( t == null) {
            chatConsole.println("Command successfully invoked");
            chatConsole.newline();
            return true;
        } else {
            chatConsole.println("ERROR: " + t.getMessage());
            chatConsole.newline();
            return false;
        }
    }

    /**
     * Prints the command line help
     *
     * @param chatConsole
     */
    protected static void printCommandlineHelp(ChatConsoleOutputStream chatConsole) {
        chatConsole.println("invoke syntax:");
        chatConsole.println("invoke <target> [<property1>=<value1> [<property2=value2>]]*");
        chatConsole.newline();
    }

    /**
     * Executes a {@link CommandMessage} on the current environment.
     *
     * @param clt the context to use to execute the remote command.
     * @return
     */
    protected Throwable executeCommand(CommandListenerTask clt, InvokeCommandMessage invokeCommandMessage) {
        Map<String, String> parameters = InvokeCommandMessage.getParameters(invokeCommandMessage);
        String command = InvokeCommandMessage.getCommand(invokeCommandMessage);
        return executeCommand(clt, command, parameters);
    }

    protected Throwable executeCommand(CommandListenerTask clt, String command, Map<String,String> parameters) {
        String key;
        for (String s : parameters.keySet()) {
            key = s;
            clt.getProject().setProperty(key, parameters.get(key));
        }
        try {
            clt.log("Executing target: " + command, Project.MSG_INFO);
            clt.getProject().executeTarget(command);
        } catch (BuildException be) {
            be.printStackTrace();
            clt.log("An error occurred during execution of command '" + command  + "' : " + be.getMessage(), Project.MSG_ERR);
            return be;
        }
        return null;
    }

}
